// Copyright 2024-2025 Golem Cloud
//
// Licensed under the Golem Source License v1.0 (the "License");
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://license.golem.cloud/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

use super::GatewayBindingCompiled;
use super::path_pattern::AllPathPatterns;
use super::{CompiledRoute, SecuritySchemeDetails};
use golem_common::model::http_api_definition::{
    HttpApiDefinitionName, HttpApiDefinitionVersion, RouteMethod,
};
use golem_common::model::security_scheme::SecuritySchemeId;
use golem_wasm::analysis::{AnalysedType, NameTypePair};
use indexmap::IndexMap;
use rib::RibInputTypeInfo;
use std::collections::{BTreeMap, HashMap};

// Constants for OpenAPI extensions
const GOLEM_DISABLED_EXTENSION: &str = "x-golem-disabled";

/// Trait that any route type must implement to be used for OpenAPI generation
pub trait HttpApiRoute {
    fn security_scheme_missing(&self) -> bool;
    fn security_scheme(&self) -> Option<SecuritySchemeId>;
    fn method(&self) -> &RouteMethod;
    fn path(&self) -> &AllPathPatterns;
    fn binding(&self) -> &GatewayBindingCompiled;
}

impl HttpApiRoute for CompiledRoute {
    fn security_scheme_missing(&self) -> bool {
        false
    }
    fn security_scheme(&self) -> Option<SecuritySchemeId> {
        self.security_scheme
    }
    fn method(&self) -> &RouteMethod {
        &self.method
    }
    fn path(&self) -> &AllPathPatterns {
        &self.path
    }
    fn binding(&self) -> &GatewayBindingCompiled {
        &self.binding
    }
}

/// Main wrapper type
pub struct HttpApiDefinitionOpenApiSpec(pub openapiv3::OpenAPI);

impl HttpApiDefinitionOpenApiSpec {
    pub async fn from_routes<'a, T, I>(
        http_api_definition_name: &HttpApiDefinitionName,
        http_api_definition_version: &HttpApiDefinitionVersion,
        routes: I,
        security_schemes: &HashMap<SecuritySchemeId, SecuritySchemeDetails>,
    ) -> Result<Self, String>
    where
        T: 'a + HttpApiRoute + ?Sized,
        I: IntoIterator<Item = &'a T>,
    {
        let mut open_api = create_base_openapi(
            http_api_definition_name,
            http_api_definition_version,
            security_schemes,
        );

        let mut paths = BTreeMap::new();
        for route in routes {
            add_route_to_paths(route, &mut paths, security_schemes).await?;
        }

        open_api.paths.paths = paths
            .into_iter()
            .map(|(k, v)| (k, openapiv3::ReferenceOr::Item(v)))
            .collect();

        Ok(HttpApiDefinitionOpenApiSpec(open_api))
    }
}

// --------------------- Base OpenAPI structure ---------------------

fn create_base_openapi(
    http_api_definition_name: &HttpApiDefinitionName,
    http_api_definition_version: &HttpApiDefinitionVersion,
    security_schemes: &HashMap<SecuritySchemeId, SecuritySchemeDetails>,
) -> openapiv3::OpenAPI {
    let mut open_api = openapiv3::OpenAPI {
        openapi: "3.0.0".to_string(),
        info: openapiv3::Info {
            title: http_api_definition_name.0.to_string(),
            description: None,
            terms_of_service: None,
            contact: None,
            license: None,
            version: http_api_definition_version.0.to_string(),
            extensions: Default::default(),
        },
        ..Default::default()
    };

    let components = openapiv3::Components::default();
    let mut spec_security_schemes = components.security_schemes;
    for security_scheme in security_schemes.values() {
        let openid_config_url = format!(
            "{}/.well-known/openid-configuration",
            security_scheme.provider_type.issuer_url().url()
        );

        let scheme = openapiv3::SecurityScheme::OpenIDConnect {
            open_id_connect_url: openid_config_url,
            description: Some(format!(
                "OpenID Connect provider for {}",
                security_scheme.name
            )),
            extensions: Default::default(),
        };

        spec_security_schemes.insert(
            security_scheme.name.0.clone(),
            openapiv3::ReferenceOr::Item(scheme),
        );
    }

    open_api.components = Some(openapiv3::Components::default());
    open_api
}

// --------------------- Process routes ---------------------

async fn add_route_to_paths<T: HttpApiRoute + ?Sized>(
    route: &T,
    paths: &mut BTreeMap<String, openapiv3::PathItem>,
    security_schemes: &HashMap<SecuritySchemeId, SecuritySchemeDetails>,
) -> Result<(), String> {
    let path_str = route.path().to_string();
    let path_item = paths.entry(path_str.clone()).or_default();

    let mut operation = openapiv3::Operation::default();

    add_parameters(&mut operation, route);
    add_request_body(&mut operation, route);
    add_responses(&mut operation, route);
    add_security(&mut operation, route, security_schemes);

    add_operation_to_path_item(path_item, route.method(), operation)?;

    Ok(())
}

// --------------------- Parameters ---------------------

type ParameterTuple = (String, openapiv3::Schema);

fn add_parameters<T: HttpApiRoute + ?Sized>(operation: &mut openapiv3::Operation, route: &T) {
    let (path_params, query_params, header_params) = get_parameters(route);

    for (name, schema) in path_params {
        operation
            .parameters
            .push(openapiv3::ReferenceOr::Item(create_path_parameter(
                &name, schema,
            )));
    }
    for (name, schema) in query_params {
        operation
            .parameters
            .push(openapiv3::ReferenceOr::Item(create_query_parameter(
                &name, schema,
            )));
    }
    for (name, schema) in header_params {
        operation
            .parameters
            .push(openapiv3::ReferenceOr::Item(create_header_parameter(
                &name, schema,
            )));
    }
}

fn get_parameters<T: HttpApiRoute + ?Sized>(
    route: &T,
) -> (
    Vec<ParameterTuple>,
    Vec<ParameterTuple>,
    Vec<ParameterTuple>,
) {
    let mut path_params = Vec::new();
    let mut query_params = Vec::new();
    let mut header_params = Vec::new();

    match route.binding() {
        GatewayBindingCompiled::Worker(worker) => {
            if let Some(request) = worker
                .idempotency_key_compiled
                .as_ref()
                .and_then(|c| c.rib_input.types.get("request"))
            {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
            if let Some(request) = worker
                .invocation_context_compiled
                .as_ref()
                .and_then(|c| c.rib_input.types.get("request"))
            {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
            if let Some(request) = worker.response_compiled.rib_input.types.get("request") {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
        }
        GatewayBindingCompiled::FileServer(file_server) => {
            if let Some(request) = file_server
                .worker_name_compiled
                .rib_input
                .types
                .get("request")
            {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
            if let Some(request) = file_server.response_compiled.rib_input.types.get("request") {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
        }
        GatewayBindingCompiled::HttpHandler(handler) => {
            if let Some(request) = handler.worker_name_compiled.rib_input.types.get("request") {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
            if let Some(request) = handler
                .idempotency_key_compiled
                .as_ref()
                .and_then(|c| c.rib_input.types.get("request"))
            {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
            if let Some(request) = handler
                .invocation_context_compiled
                .as_ref()
                .and_then(|c| c.rib_input.types.get("request"))
            {
                extract_parameters_from_record(
                    request,
                    &mut path_params,
                    &mut query_params,
                    &mut header_params,
                );
            }
        }
        GatewayBindingCompiled::HttpCorsPreflight(_) => {}
        GatewayBindingCompiled::SwaggerUi(_) => {}
    }

    (path_params, query_params, header_params)
}

fn extract_parameters_from_record(
    record: &AnalysedType,
    path_parameters: &mut Vec<ParameterTuple>,
    query_parameters: &mut Vec<ParameterTuple>,
    header_parameters: &mut Vec<ParameterTuple>,
) {
    if let AnalysedType::Record(rec) = record {
        for field_name in ["path", "query", "headers"] {
            if let Some(field) = rec.fields.iter().find(|f| f.name == field_name)
                && let AnalysedType::Record(sub_rec) = &field.typ
            {
                for sub_field in &sub_rec.fields {
                    let schema = create_schema_from_analysed_type(&sub_field.typ);
                    match field_name {
                        "path" => path_parameters.push((sub_field.name.clone(), schema)),
                        "query" => query_parameters.push((sub_field.name.clone(), schema)),
                        "headers" => header_parameters.push((sub_field.name.clone(), schema)),
                        _ => {}
                    }
                }
            }
        }
    }
}

fn create_path_parameter(name: &str, schema: openapiv3::Schema) -> openapiv3::Parameter {
    openapiv3::Parameter::Path {
        parameter_data: openapiv3::ParameterData {
            name: name.to_string(),
            description: Some(format!("Path parameter: {name}")),
            required: true,
            deprecated: None,
            explode: Some(false),
            format: openapiv3::ParameterSchemaOrContent::Schema(openapiv3::ReferenceOr::Item(
                schema,
            )),
            example: None,
            examples: Default::default(),
            extensions: Default::default(),
        },
        style: openapiv3::PathStyle::Simple,
    }
}

fn create_query_parameter(name: &str, schema: openapiv3::Schema) -> openapiv3::Parameter {
    openapiv3::Parameter::Query {
        parameter_data: openapiv3::ParameterData {
            name: name.to_string(),
            description: Some(format!("Query parameter: {name}")),
            required: true,
            deprecated: None,
            explode: Some(false),
            format: openapiv3::ParameterSchemaOrContent::Schema(openapiv3::ReferenceOr::Item(
                schema,
            )),
            example: None,
            examples: Default::default(),
            extensions: Default::default(),
        },
        style: openapiv3::QueryStyle::Form,
        allow_empty_value: Some(false),
        allow_reserved: false,
    }
}

fn create_header_parameter(name: &str, schema: openapiv3::Schema) -> openapiv3::Parameter {
    openapiv3::Parameter::Header {
        parameter_data: openapiv3::ParameterData {
            name: name.to_string(),
            description: Some(format!("Header parameter: {name}")),
            required: true,
            deprecated: None,
            explode: Some(false),
            format: openapiv3::ParameterSchemaOrContent::Schema(openapiv3::ReferenceOr::Item(
                schema,
            )),
            example: None,
            examples: Default::default(),
            extensions: Default::default(),
        },
        style: openapiv3::HeaderStyle::Simple,
    }
}

// --------------------- Request body ---------------------

fn add_request_body<T: HttpApiRoute + ?Sized>(operation: &mut openapiv3::Operation, route: &T) {
    match route.binding() {
        GatewayBindingCompiled::Worker(worker) => {
            if let Some(rb) = create_request_body(&worker.response_compiled.rib_input) {
                operation.request_body = Some(openapiv3::ReferenceOr::Item(rb));
            }
        }
        GatewayBindingCompiled::FileServer(file_server) => {
            if let Some(rb) = create_request_body(&file_server.response_compiled.rib_input) {
                operation.request_body = Some(openapiv3::ReferenceOr::Item(rb));
            }
        }
        GatewayBindingCompiled::HttpHandler(_) => {}
        GatewayBindingCompiled::HttpCorsPreflight(_) => {}
        GatewayBindingCompiled::SwaggerUi(_) => {}
    }
}

fn create_request_body(rib: &RibInputTypeInfo) -> Option<openapiv3::RequestBody> {
    if let Some(schema) = determine_request_body_schema(rib) {
        let mut content = IndexMap::new();
        content.insert(
            "application/json".to_string(),
            openapiv3::MediaType {
                schema: Some(openapiv3::ReferenceOr::Item(schema)),
                ..Default::default()
            },
        );
        Some(openapiv3::RequestBody {
            description: Some("Request payload".to_string()),
            content,
            required: true,
            extensions: Default::default(),
        })
    } else {
        None
    }
}

fn determine_request_body_schema(rib: &RibInputTypeInfo) -> Option<openapiv3::Schema> {
    if let Some(AnalysedType::Record(req)) = rib.types.get("request")
        && let Some(body) = req.fields.iter().find(|f| f.name == "body")
    {
        return Some(create_schema_from_analysed_type(&body.typ));
    }
    None
}

// --------------------- Responses ---------------------

fn add_responses<T: HttpApiRoute + ?Sized>(operation: &mut openapiv3::Operation, route: &T) {
    let (response_schema, headers, explicit_status) =
        determine_response_schema_content_type_headers(route);

    let mut response = openapiv3::Response {
        description: "Response".to_string(),
        ..Default::default()
    };

    match response_schema {
        DeterminedResponseBodySchema::Known {
            schema,
            content_type,
        } => {
            let mut content = IndexMap::new();
            content.insert(
                content_type.clone(),
                openapiv3::MediaType {
                    schema: Some(openapiv3::ReferenceOr::Item(*schema)),
                    ..Default::default()
                },
            );
            response.content = content;
        }
        DeterminedResponseBodySchema::Unknown => {
            let mut content = IndexMap::new();
            content.insert(
                "*/*".to_string(),
                openapiv3::MediaType {
                    ..Default::default()
                },
            );
            response.content = content;
        }
        DeterminedResponseBodySchema::NoBody => {
            response.content = IndexMap::new(); // explicitly no body
        }
    }

    // Add headers if any
    if let Some(headers_map) = headers {
        let mut response_headers = IndexMap::new();
        for (name, schema) in headers_map {
            let description = format!("Response header: {}", name);
            response_headers.insert(
                name.clone(),
                openapiv3::ReferenceOr::Item(openapiv3::Header {
                    description: Some(description),
                    required: true,
                    deprecated: None,
                    format: openapiv3::ParameterSchemaOrContent::Schema(
                        openapiv3::ReferenceOr::Item(schema),
                    ),
                    example: None,
                    examples: Default::default(),
                    extensions: Default::default(),
                    style: openapiv3::HeaderStyle::Simple,
                }),
            );
        }
        response.headers = response_headers;
    }

    // Insert response: explicit status if given, otherwise default
    if let Some(status) = explicit_status {
        operation.responses.responses.insert(
            openapiv3::StatusCode::Code(status),
            openapiv3::ReferenceOr::Item(response),
        );
    } else {
        operation.responses.default = Some(openapiv3::ReferenceOr::Item(response));
    }
}

enum DeterminedResponseBodySchema {
    Unknown,
    Known {
        schema: Box<openapiv3::Schema>,
        content_type: String,
    },
    NoBody,
}

fn determine_response_schema_content_type_headers<T: HttpApiRoute + ?Sized>(
    route: &T,
) -> (
    DeterminedResponseBodySchema,
    Option<IndexMap<String, openapiv3::Schema>>,
    Option<u16>,
) {
    match route.binding() {
        GatewayBindingCompiled::Worker(worker) => {
            if let Some(output_info) = &worker.response_compiled.rib_output
                && let AnalysedType::Record(record) = &output_info.analysed_type
            {
                let (headers, body) = extract_response_header_and_body_types(&record.fields);
                let headers_schema = headers.map(|h| {
                    let mut map = IndexMap::new();
                    if let AnalysedType::Record(r) = h {
                        for f in &r.fields {
                            map.insert(f.name.clone(), create_schema_from_analysed_type(&f.typ));
                        }
                    }
                    map
                });

                let response_schema = body
                    .map(|b| DeterminedResponseBodySchema::Known {
                        schema: Box::new(create_schema_from_analysed_type(&b)),
                        content_type: "application/json".to_string(),
                    })
                    .unwrap_or(DeterminedResponseBodySchema::NoBody);

                return (response_schema, headers_schema, None);
            }
            (DeterminedResponseBodySchema::Unknown, None, None)
        }
        GatewayBindingCompiled::SwaggerUi(_) => {
            let schema = create_schema_from_analysed_type(&AnalysedType::Str(
                golem_wasm::analysis::TypeStr {},
            ));
            (
                DeterminedResponseBodySchema::Known {
                    schema: Box::new(schema),
                    content_type: "text/html".to_string(),
                },
                None,
                Some(200),
            )
        }
        GatewayBindingCompiled::HttpCorsPreflight(_) => {
            let mut headers = IndexMap::new();
            let string_schema = openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::String(
                    openapiv3::StringType {
                        format: openapiv3::VariantOrUnknownOrEmpty::Empty,
                        pattern: None,
                        enumeration: vec![],
                        min_length: None,
                        max_length: None,
                    },
                )),
            };
            for h in [
                "Access-Control-Allow-Origin",
                "Access-Control-Allow-Headers",
                "Access-Control-Allow-Methods",
            ] {
                headers.insert(h.to_string(), string_schema.clone());
            }
            (
                DeterminedResponseBodySchema::NoBody,
                Some(headers),
                Some(200),
            )
        }
        _ => (DeterminedResponseBodySchema::Unknown, None, None),
    }
}

fn extract_response_header_and_body_types(
    fields: &[NameTypePair],
) -> (Option<AnalysedType>, Option<AnalysedType>) {
    let mut headers = None;
    let mut body = None;

    for f in fields {
        match f.name.as_str() {
            "headers" => headers = Some(f.typ.clone()),
            "body" => body = Some(f.typ.clone()),
            _ => {}
        }
    }

    (headers, body)
}

// --------------------- Security ---------------------

fn add_security<T: HttpApiRoute + ?Sized>(
    operation: &mut openapiv3::Operation,
    route: &T,
    security_schemes_map: &HashMap<SecuritySchemeId, SecuritySchemeDetails>,
) {
    if route.security_scheme_missing() {
        operation.extensions.insert(
            GOLEM_DISABLED_EXTENSION.to_string(),
            serde_json::json!({ "reason": "security_scheme_missing" }),
        );
        return;
    }

    // If the route references a scheme
    if let Some(security_scheme_id) = route.security_scheme() {
        let details = security_schemes_map
            .get(&security_scheme_id)
            .expect("Failed to find security scheme even though it's not marked as missing");
        let scopes_vec: Vec<String> = details.scopes.iter().map(|s| s.to_string()).collect();

        let mut requirement: indexmap::IndexMap<String, Vec<String>> = indexmap::IndexMap::new();
        requirement.insert(details.name.0.clone(), scopes_vec);

        operation.security = Some(vec![requirement]);
    }
}

// --------------------- Helpers ---------------------

fn add_operation_to_path_item(
    path_item: &mut openapiv3::PathItem,
    method: &RouteMethod,
    operation: openapiv3::Operation,
) -> Result<(), String> {
    match method {
        RouteMethod::Get => path_item.get = Some(operation),
        RouteMethod::Post => path_item.post = Some(operation),
        RouteMethod::Put => path_item.put = Some(operation),
        RouteMethod::Delete => path_item.delete = Some(operation),
        RouteMethod::Patch => path_item.patch = Some(operation),
        RouteMethod::Options => path_item.options = Some(operation),
        RouteMethod::Head => path_item.head = Some(operation),
        RouteMethod::Trace => path_item.trace = Some(operation),
        RouteMethod::Connect => return Err("CONNECT not supported".to_string()),
    }
    Ok(())
}

fn create_integer_schema(
    format: openapiv3::IntegerFormat,
    min: Option<i64>,
    max: Option<i64>,
) -> openapiv3::Schema {
    let schema_data = openapiv3::SchemaData::default();
    openapiv3::Schema {
        schema_data,
        schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Integer(
            openapiv3::IntegerType {
                format: openapiv3::VariantOrUnknownOrEmpty::Item(format),
                minimum: min,
                maximum: max,
                multiple_of: None,
                exclusive_minimum: false,
                exclusive_maximum: false,
                enumeration: vec![],
            },
        )),
    }
}

fn create_schema_from_analysed_type(analysed_type: &AnalysedType) -> openapiv3::Schema {
    use golem_wasm::analysis::AnalysedType;

    match analysed_type {
        AnalysedType::Bool(_) => openapiv3::Schema {
            schema_data: Default::default(),
            schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(
                openapiv3::BooleanType::default(),
            )),
        },
        AnalysedType::U8(_) => {
            create_integer_schema(openapiv3::IntegerFormat::Int32, Some(0), Some(255))
        }
        AnalysedType::U16(_) => {
            create_integer_schema(openapiv3::IntegerFormat::Int32, Some(0), Some(65535))
        }
        AnalysedType::U32(_) => {
            create_integer_schema(openapiv3::IntegerFormat::Int32, Some(0), None)
        }
        AnalysedType::U64(_) => {
            create_integer_schema(openapiv3::IntegerFormat::Int64, Some(0), None)
        }
        AnalysedType::S8(_) => {
            create_integer_schema(openapiv3::IntegerFormat::Int32, Some(-128), Some(127))
        }
        AnalysedType::S16(_) => {
            create_integer_schema(openapiv3::IntegerFormat::Int32, Some(-32768), Some(32767))
        }
        AnalysedType::S32(_) => create_integer_schema(openapiv3::IntegerFormat::Int32, None, None),
        AnalysedType::S64(_) => create_integer_schema(openapiv3::IntegerFormat::Int64, None, None),

        AnalysedType::F32(_) => {
            let sd = openapiv3::SchemaData::default();
            openapiv3::Schema {
                schema_data: sd,
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Number(
                    openapiv3::NumberType {
                        format: openapiv3::VariantOrUnknownOrEmpty::Item(
                            openapiv3::NumberFormat::Float,
                        ),
                        multiple_of: None,
                        exclusive_minimum: false,
                        exclusive_maximum: false,
                        minimum: None,
                        maximum: None,
                        enumeration: vec![],
                    },
                )),
            }
        }
        AnalysedType::F64(_) => {
            let sd = openapiv3::SchemaData::default();
            openapiv3::Schema {
                schema_data: sd,
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Number(
                    openapiv3::NumberType {
                        format: openapiv3::VariantOrUnknownOrEmpty::Item(
                            openapiv3::NumberFormat::Double,
                        ),
                        multiple_of: None,
                        exclusive_minimum: false,
                        exclusive_maximum: false,
                        minimum: None,
                        maximum: None,
                        enumeration: vec![],
                    },
                )),
            }
        }
        AnalysedType::Str(_) => openapiv3::Schema {
            schema_data: Default::default(),
            schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::String(
                openapiv3::StringType {
                    format: openapiv3::VariantOrUnknownOrEmpty::Empty,
                    pattern: None,
                    enumeration: vec![],
                    min_length: None,
                    max_length: None,
                },
            )),
        },
        AnalysedType::List(type_list) => {
            let items = openapiv3::ReferenceOr::Item(Box::new(create_schema_from_analysed_type(
                &type_list.inner,
            )));
            openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Array(
                    openapiv3::ArrayType {
                        items: Some(items),
                        min_items: None,
                        max_items: None,
                        unique_items: false,
                    },
                )),
            }
        }
        AnalysedType::Tuple(type_tuple) => {
            let min_items = Some(type_tuple.items.len());
            let max_items = Some(type_tuple.items.len());

            let items = openapiv3::ReferenceOr::Item(Box::new(openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Object(
                    Default::default(),
                )),
            }));
            let array_schema = openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Array(
                    openapiv3::ArrayType {
                        items: Some(items),
                        min_items,
                        max_items,
                        unique_items: false,
                    },
                )),
            };

            let schema_data = openapiv3::SchemaData {
                description: Some("Tuple type".to_string()),
                ..Default::default()
            };
            openapiv3::Schema {
                schema_data,
                schema_kind: array_schema.schema_kind,
            }
        }
        AnalysedType::Record(type_record) => {
            let mut properties = IndexMap::new();
            let mut required = Vec::new();
            for field in &type_record.fields {
                let field_schema = create_schema_from_analysed_type(&field.typ);
                let is_nullable = field_schema.schema_data.nullable;
                properties.insert(
                    field.name.clone(),
                    openapiv3::ReferenceOr::Item(Box::new(field_schema)),
                );
                if !is_nullable {
                    required.push(field.name.clone());
                }
            }
            openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Object(
                    openapiv3::ObjectType {
                        properties,
                        required,
                        additional_properties: None,
                        min_properties: None,
                        max_properties: None,
                    },
                )),
            }
        }
        AnalysedType::Variant(type_variant) => {
            let mut one_of = Vec::new();
            for case in &type_variant.cases {
                let case_name = &case.name;
                if let Some(case_type) = &case.typ {
                    let case_schema = create_schema_from_analysed_type(case_type);
                    let mut properties = IndexMap::new();
                    properties.insert(
                        case_name.clone(),
                        openapiv3::ReferenceOr::Item(Box::new(case_schema)),
                    );
                    let required = vec![case_name.clone()];
                    let schema = openapiv3::Schema {
                        schema_data: Default::default(),
                        schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Object(
                            openapiv3::ObjectType {
                                properties,
                                required,
                                additional_properties: None,
                                min_properties: None,
                                max_properties: None,
                            },
                        )),
                    };
                    one_of.push(openapiv3::ReferenceOr::Item(schema));
                } else {
                    let schema = openapiv3::Schema {
                        schema_data: Default::default(),
                        schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::String(
                            openapiv3::StringType {
                                format: openapiv3::VariantOrUnknownOrEmpty::Empty,
                                pattern: None,
                                enumeration: vec![],
                                min_length: None,
                                max_length: None,
                            },
                        )),
                    };
                    one_of.push(openapiv3::ReferenceOr::Item(schema));
                }
            }
            openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::OneOf { one_of },
            }
        }
        AnalysedType::Enum(type_enum) => {
            let enum_values: Vec<Option<String>> =
                type_enum.cases.iter().map(|c| Some(c.clone())).collect();
            openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::String(
                    openapiv3::StringType {
                        format: openapiv3::VariantOrUnknownOrEmpty::Empty,
                        pattern: None,
                        enumeration: enum_values,
                        min_length: None,
                        max_length: None,
                    },
                )),
            }
        }
        AnalysedType::Option(type_option) => {
            let mut schema = create_schema_from_analysed_type(&type_option.inner);
            schema.schema_data.nullable = true;
            schema
        }
        AnalysedType::Result(type_result) => {
            let ok_type = match &type_result.ok {
                Some(b) => &**b,
                None => &AnalysedType::Str(golem_wasm::analysis::TypeStr {}),
            };
            let err_type = match &type_result.err {
                Some(b) => &**b,
                None => &AnalysedType::Str(golem_wasm::analysis::TypeStr {}),
            };
            let ok_schema = create_schema_from_analysed_type(ok_type);
            let err_schema = create_schema_from_analysed_type(err_type);

            let mut ok_properties = IndexMap::new();
            ok_properties.insert(
                "ok".to_string(),
                openapiv3::ReferenceOr::Item(Box::new(ok_schema)),
            );
            let ok_required = vec!["ok".to_string()];
            let ok_object_schema = openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Object(
                    openapiv3::ObjectType {
                        properties: ok_properties,
                        required: ok_required,
                        additional_properties: None,
                        min_properties: None,
                        max_properties: None,
                    },
                )),
            };

            let mut err_properties = IndexMap::new();
            err_properties.insert(
                "err".to_string(),
                openapiv3::ReferenceOr::Item(Box::new(err_schema)),
            );
            let err_required = vec!["err".to_string()];
            let err_object_schema = openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Object(
                    openapiv3::ObjectType {
                        properties: err_properties,
                        required: err_required,
                        additional_properties: None,
                        min_properties: None,
                        max_properties: None,
                    },
                )),
            };

            openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::OneOf {
                    one_of: vec![
                        openapiv3::ReferenceOr::Item(ok_object_schema),
                        openapiv3::ReferenceOr::Item(err_object_schema),
                    ],
                },
            }
        }
        AnalysedType::Flags(type_flags) => {
            let enum_values: Vec<Option<String>> =
                type_flags.names.iter().map(|n| Some(n.clone())).collect();
            let items_schema = openapiv3::Schema {
                schema_data: Default::default(),
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::String(
                    openapiv3::StringType {
                        format: openapiv3::VariantOrUnknownOrEmpty::Empty,
                        pattern: None,
                        enumeration: enum_values,
                        min_length: None,
                        max_length: None,
                    },
                )),
            };
            openapiv3::Schema {
                schema_data: openapiv3::SchemaData {
                    description: Some("Flags type - array of flag names".to_string()),
                    ..Default::default()
                },
                schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::Array(
                    openapiv3::ArrayType {
                        items: Some(openapiv3::ReferenceOr::Item(Box::new(items_schema))),
                        min_items: Some(0),
                        max_items: Some(type_flags.names.len()),
                        unique_items: true,
                    },
                )),
            }
        }
        AnalysedType::Chr(_) => openapiv3::Schema {
            schema_data: openapiv3::SchemaData {
                description: Some("Unicode character".to_string()),
                ..Default::default()
            },
            schema_kind: openapiv3::SchemaKind::Type(openapiv3::Type::String(
                openapiv3::StringType {
                    format: openapiv3::VariantOrUnknownOrEmpty::Empty,
                    pattern: Some("^.{1}$".to_string()),
                    enumeration: vec![],
                    min_length: Some(1),
                    max_length: Some(1),
                },
            )),
        },
        AnalysedType::Handle(_) => unimplemented!(),
    }
}
